package ldh.im.fxbase.service;

import javafx.application.Platform;
import ldh.im.ImConfig;
import ldh.im.client.*;
import ldh.im.dto.base.Dto;
import ldh.im.dto.base.OkResp;
import ldh.im.dto.base.ResultResp;
import ldh.im.dto.business.file.FileDownloadReq;
import ldh.im.dto.business.file.FileInfoReq;
import ldh.im.dto.business.file.FileUploadSuccessReq;
import ldh.im.dto.business.item.FileItem;
import ldh.im.exception.BusinessException;
import ldh.im.exception.ImServiceException;
import ldh.im.fxbase.dao.ImMessageDao;
import ldh.im.fxbase.data.pojo.*;
import ldh.im.fxbase.data.pojo.status.ImCollectContent;
import ldh.im.fxbase.data.pojo.status.ImMessageFileStatus;
import ldh.im.fxbase.util.*;
import ldh.im.fxbase.rpc.service.RpcMessageService;
import ldh.im.fxbase.thread.DynamicThreadPool;
import ldh.im.listener.FileListener;
import ldh.im.listener.FileProgressListener;
import ldh.im.util.ConfigUtil;
import ldh.im.util.DateUtil;
import ldh.rpc.util.JsonUtil;
import ldh.im.util.UploadFileUtil;
import ldh.rpc.TerminalType;
import ldh.rpc.cache.CacheService;
import ldh.rpc.dto.ExceptionResp;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.concurrent.*;
import java.util.function.Consumer;

public class DefaultFileDownloadAndUploadService implements FileDownloadAndUploadService {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFileDownloadAndUploadService.class);

    private ClientChannel clientChannel;
    private CacheService cacheService;

    private ImMessageDao imMessageDao = BeanFactory.getInstance().getBean(ImMessageDao.class);
    private MessageService messageService = BeanFactory.getInstance().getBean(MessageService.class);
    private FileService fileService = BeanFactory.getInstance().getBean(FileService.class);

//    private ExecutorService uploadExecutorService = Executors.newFixedThreadPool(3);
//    private ExecutorService downloadExecutorService = Executors.newFixedThreadPool(3);
    private DynamicThreadPool uploadExecutorService = new DynamicThreadPool(3, 2, TimeUnit.MINUTES);
    private DynamicThreadPool downloadExecutorService = new DynamicThreadPool(3, 2, TimeUnit.MINUTES);
    private RpcMessageService rpcMessageService = RpcUtil.getRpcBootstrap().getRpcConfig().getClientBean(RpcMessageService.class);

    public DefaultFileDownloadAndUploadService(CacheService cacheService) {
        this.cacheService = cacheService;
    }


    @Override
    public void sendFile(ImMessage message, FileProgressListener fileProgressListener) throws Exception {
        String fileUrl = FileUtil.getFilePath(message);
        LOGGER.info("uploading file {}, fileId: {}", fileUrl, message.getServerId());
        uploadExecutorService.execute(()->{
            FileUploadChannelService fileUploadChannelService = null;
            try {
                String address = ConfigUtil.getString("file.address");
                int port = ConfigUtil.getInt("file.port", 9190);
                fileUploadChannelService = new FileUploadChannelService(address, port, cacheService, fileProgressListener);
                FileInfoReq fileInfoReq = new FileInfoReq();
                String filePath = fileUrl.replace("file:\\", "");
                File file = new File(filePath);

                CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
                String userId = (String) cacheService.getCache(ImConfig.LOGIN_USER_ID);
                fileInfoReq.setUserId(userId);
                fileInfoReq.setSessionId(message.getSessionId());
                fileInfoReq.setFileServerId(message.getServerId());
                fileInfoReq.setFileUrl(filePath);
                fileInfoReq.setTenantCode((String)cacheService.getCache(ImConfig.TENANT_ID));
                fileInfoReq.setTerminalType(TerminalType.pc);
                try(FileInputStream inputStream = new FileInputStream(file)) {
                    int size = inputStream.available();
                    String md5 = DigestUtils.sha512Hex(inputStream);
                    fileInfoReq.setFileName(file.getName());
                    fileInfoReq.setFileSize(size);
                    String authCode = DigestUtils.sha1Hex(fileInfoReq.getFileUrl() + "_" + fileInfoReq.getFileSize() + "_" + fileInfoReq.getFileServerId());
                    fileInfoReq.setFileAuthCode(authCode);
                    fileInfoReq.setFileMd5(md5);
                }
                fileUploadChannelService.sendFile(fileInfoReq, (baseClientChannelService, resultResp) -> {
                    if (resultResp instanceof OkResp) {
                        ThreadPoolUtil.submit(()->{
                            ExceptionUtil.catchException(()->{
                                imMessageDao.changeMessageFileStatus(message.getId(), ImMessageFileStatus.uploaded);
                                FileUploadSuccessReq fileUploadSuccessReq = new FileUploadSuccessReq();
                                fileUploadSuccessReq.setMessageId(message.getServerId());
                                rpcMessageService.sendFileUploadSuccess(fileUploadSuccessReq);
                                baseClientChannelService.close();
                                return null;
                            }, "下载成功后业务处理");
                        });
                    } else {
                        ExceptionResp exceptionResp = (ExceptionResp) resultResp;
                        LOGGER.error("sendFile error " + exceptionResp.getErrorStatus() + ":" + ((ExceptionResp) resultResp).getErrorMsg());
                        FxThreadUtil.runLater(()->{
                            DialogUtil.alert("错误", ((ExceptionResp) resultResp).getErrorMsg());
                        });
                    }
                });
            } catch (Exception e) {
                LOGGER.error("文件上传失败！", e);

                if (fileUploadChannelService != null) {
                    fileUploadChannelService.close();
                }
            }
        });
    };

    private void downloadFile(String dir, Long sessionId, String fileServiceId, String fileUrl, FileListener fileListener) {
        downloadExecutorService.execute(()->{
            FileDownloadChannelService fileDownloadChannelService = null;
            try {
                String address = ConfigUtil.getString("file.address");
                int port = ConfigUtil.getInt("file.port", 9190);
                fileDownloadChannelService = new FileDownloadChannelService(address, port, cacheService, fileListener);

                FileDownloadReq fileDownloadReq = new FileDownloadReq();
                fileDownloadReq.setFileServerId(fileServiceId);
                CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
                String userId = (String) cacheService.getCache(ImConfig.LOGIN_USER_ID);
                fileDownloadReq.setUserId(userId);
                fileDownloadReq.setSessionId(sessionId);
                fileDownloadReq.setFileUrl(fileUrl);
                fileDownloadReq.setLocalDir(dir);
                fileDownloadReq.setTerminalType(TerminalType.pc);
                String authCode = DigestUtils.sha1Hex(fileDownloadReq.getFileUrl() + "_" + fileDownloadReq.getUserId() + "_" + fileDownloadReq.getFileServerId());
                fileDownloadReq.setFileAuthCode(authCode);

                fileDownloadChannelService.downloadFile(fileDownloadReq);
            } catch (Exception e) {
                FxThreadUtil.runLater(()->DialogUtil.alert("错误", e.getMessage()));
                e.printStackTrace();
                if (fileDownloadChannelService != null) {
                    fileDownloadChannelService.close();
                }
            }
        });
    }

    /**
     * 接受到附件的消息，会自动触发文件下载。
     * 当文件上传成功后，会自动触发一次文件下载
     * 需要进行并发处理
     * @param imMessage
     * @param consumer
     */
    @Override
    public void downloadFile(ImMessage imMessage, FileProgressListener fileListener, Consumer<ImMessage> consumer) {
        if (!imMessage.getMessageType().isFile()) return;
        if (imMessage.getFileStatus() == ImMessageFileStatus.downloaded) return;
        String cacheDownloadKey = ObjectUtil.mergeToString("downloadFile", imMessage.getId());
        if (cacheService.getCache(cacheDownloadKey) != null) {
            return;
        }
        cacheService.setCache(cacheDownloadKey, imMessage, DateUtil.toDate(LocalDateTime.now().plusMinutes(30)));

        LOGGER.info("download file begin, messageId: {}, content: {}", imMessage.getId(), imMessage.getContent());
        CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
        String userId = (String) cacheService.getCache(ImConfig.LOGIN_USER_ID);

        String dir = ConfigUtil.buildFileDir("download", imMessage.getCreateDateTime(), userId);
        MessageExt messageExt = imMessage.getMessageExt();
        String fileUrl = messageExt.getFileUrl();

        // 下载文件已经下载成功，不需要再次下载
        if (fileExisted(imMessage) ) {
            if (imMessage.getFileStatus() != null && imMessage.getFileStatus() != ImMessageFileStatus.downloaded) {
                cacheService.remove(cacheDownloadKey);
                doDownloadBusiness(userId, imMessage, dir, fileUrl);

                if (consumer != null) {
                    FxThreadUtil.runLater(()-> consumer.accept(imMessage));
                }
                return;
            }
        }

        downloadFile(dir, imMessage.getSessionId(), imMessage.getServerId(), fileUrl, new FileListener() {
            @Override
            public void progress(Long sessionId, String fileServerId, long total, long currentSize) {
                if (fileListener != null) {
                    fileListener.progress(sessionId, fileServerId, total, currentSize);
                }
            }

            @Override
            public void downloadDone(Object resultResp) {
                cacheService.remove(cacheDownloadKey);
                if(!(resultResp instanceof ExceptionResp)) {
                    doDownloadBusiness(userId, imMessage, dir, fileUrl);
                    if (consumer != null) {
                        FxThreadUtil.runLater(()-> consumer.accept(imMessage));
                    }
                } else {
                    FxThreadUtil.runLater(()->DialogUtil.alert("错误", ((ExceptionResp) resultResp).getErrorMsg()));
                }
            }

            @Override
            public void error(ExceptionResp exceptionResp) {
                cacheService.remove(cacheDownloadKey);
                FxThreadUtil.runLater(()->DialogUtil.alert("错误", exceptionResp.getErrorMsg()));
            }
        });
    }

    @Override
    public void downloadFile(ImCollect imCollect) {
        if (!imCollect.getMessageType().isFile()) return;
        CacheService cacheService = RpcUtil.getRpcBootstrap().getRpcConfig().getCacheService();
        String userId = (String) cacheService.getCache(ImConfig.LOGIN_USER_ID);

        String dir = ConfigUtil.buildFileDir("collect", imCollect.getCreateTime(), userId);
        ImCollectContent collectContent = imCollect.getCollectContent();
        String fileContent = collectContent.getFileContent();
        FileItem fileItem = JsonUtil.toObject(fileContent, FileItem.class);
        String relaxPath = fileItem.getFileUrl() + fileItem.getFileName();

        // 下载文件已经下载成功，不需要再次下载
        if (FileUtil.isDownloaded(relaxPath) ) {
            return;
        }

        downloadFile(dir, imCollect.getId(), imCollect.getServerId(), relaxPath, new FileListener() {
            @Override
            public void progress(Long collectId, String fileServerId, long total, long currentSize) {

            }

            @Override
            public void downloadDone(Object resultResp) {
                if(!(resultResp instanceof ExceptionResp)) {

                } else {
                    FxThreadUtil.runLater(()->DialogUtil.alert("错误", ((ExceptionResp) resultResp).getErrorMsg()));
                }
            }

            @Override
            public void error(ExceptionResp exceptionResp) {
                FxThreadUtil.runLater(()->DialogUtil.alert("错误", exceptionResp.getErrorMsg()));
            }
        });
    }

    @Override
    public String sendDto(Dto dto) throws TimeoutException, InterruptedException {
        Dto result = getClientChannel().write(dto, 100, TimeUnit.SECONDS);
        if (result instanceof ResultResp) {
            ResultResp resultResp = (ResultResp) result;
            if (resultResp.isError()) {
                throw new ImServiceException(resultResp.getErrorCode());
            }
            return resultResp.getData().toString();
        }
        if (result instanceof OkResp) {
            OkResp okResp = (OkResp) result;
            return okResp.getOtherInfo();
        }
        throw new BusinessException("发送消息失败");
    }

    @Override
    public void close() {
        getClientChannel().close();
    }

    public ClientChannel getClientChannel() {
        if (clientChannel == null) {
            try {
                initClientChannel();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return clientChannel;
    }

    private void initClientChannel() throws InterruptedException, TimeoutException {
        String address = ConfigUtil.getString("socket.address");
        int port = ConfigUtil.getInt("socket.port", 9090);
        clientChannel = new ClientChannelService(address, port, cacheService, (t)->{
            LOGGER.info("receive data： {}", JsonUtil.toJson(t));
        });
        clientChannel.connection();
    }

    /**
     * 文件下载成功后，变更本地状态和数据
     * @param imMessage
     * @param dir
     * @param fileName
     * @throws SQLException
     */
    private void doDownloadBusiness(String userId, ImMessage imMessage, String dir, String fileName) {
        try {
            String suffix = UploadFileUtil.suffix(fileName);
            String downFileName = imMessage.getServerId() +  "." + suffix;
            String downFile = dir + File.separator + downFileName;

            imMessage.initMessageExt();

            String newFileName = buildNewFileName(dir, imMessage.getMessageExt());
            String newFile = dir + File.separator + newFileName;
            if (!Files.exists(Paths.get(ConfigUtil.getRoot() + newFile))) {
                Files.move(Paths.get(ConfigUtil.getRoot() + downFile), Paths.get(ConfigUtil.getRoot() + newFile));
            }

            ImMessageFileStatus fileStatus = ImMessageFileStatus.downloaded;
            imMessage.setFileStatus(fileStatus);
            imMessage.setContent(newFile);

            MessageExt messageExt = imMessage.getMessageExt();
            messageExt.setFileUrl(newFile);
            messageExt.setFileName(newFileName);
            imMessage.initExt();
            imMessageDao.changeMessageFileStatus(imMessage.getId(), newFile, imMessage.getExt(), fileStatus);

            // 计算图片尺寸
            messageService.handleImageSize(Arrays.asList(imMessage));

            fileService.saveFile(userId, imMessage);
        } catch (Exception e) {
            LOGGER.error("文件下载成功后，本地数据变更失败", e);
        }
    }

    // 检查文件是否已经下载
    private boolean fileExisted(ImMessage imMessage) {
        String filePath = imMessage.getContent();

        // 下载文件已经下载成功，不需要再次下载
        if (FileUtil.isDownloaded(filePath) ) {
            return true;
        }

        try {
            ImFile dbFile = fileService.getFile(imMessage.getCurrentUserId(), imMessage.getMessageExt().getMd5());
            if (dbFile != null) {
                filePath = dbFile.getPath();
                if (dbFile.getRelativePath()) {
                    return FileUtil.isDownloaded(filePath);
                } else {
                    return Files.exists(Paths.get(filePath));
                }
            }
        } catch (Exception e) {
            LOGGER.error("获取ImFile失败, error: {}", e.getMessage());
        }

        return false;
    }

    private String buildNewFileName(String dir, MessageExt messageExt) {
        String fileName = messageExt.getOriginalName();
        String filePath = dir + File.separator + fileName;

        if(!Files.exists(Paths.get(filePath))) {
            return fileName;
        }

        int i = 1;
        String suffix = UploadFileUtil.suffix(fileName);
        String originFileName = UploadFileUtil.getNoSuffixFileName(fileName);
        fileName = originFileName + (i++) + "." + suffix;
        while(Files.exists(Paths.get(fileName))) {
            fileName = originFileName + (i++) + "." + suffix;
        }
        return fileName;
    }
}
